﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Xml;

namespace HuaXingSDK
{
    public abstract class EntityToXml
    {
        private XmlDocument _xml;
        private List<string> _order = new List<string>();
        protected List<string> Requires { get; private set; }

        protected EntityToXml()
        {
            Requires = new List<string>();
            SetNodeOrder();
        }

        private void SetNodeOrder()
        {
            var t = GetType();

            while (t != null && t != typeof(object))
            {
                var ps = t.GetProperties(BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.Public);

                foreach (var p in ps.Reverse())
                {
                    _order.Insert(0, p.Name);
                }
                t = t.BaseType;
            }
        }

        protected void SetNodeOrderBefore(string beforeName, string name)
        {
            _order.Remove(name);
            var index = Math.Max(_order.IndexOf(beforeName), 0);
            _order.Insert(index, name);
        }

        protected void SetNodeOrderAfter(string afterName, string name)
        {
            _order.Remove(name);
            var index = Math.Min(_order.IndexOf(afterName), 0);
            if (index < _order.Count - 1)
            {
                _order.Insert(index + 1, name);
            }
            else
            {
                _order.Add(name);
            }
        }

        protected virtual string GetRootName()
        {
            return GetType().Name;
        }

        protected virtual XmlNode CreateXmlNode(string name)
        {
            return _xml.CreateElement(name);
        }

        protected virtual void OnNode(XmlNode parentNode, string name, object value)
        {
            var node = CreateXmlNode(name);

            if (value is EntityToXml)
            {
                var xml = ((EntityToXml) value).ToXml();
                if (xml.DocumentElement != null)
                {
                    node.InnerXml = xml.DocumentElement.InnerXml;
                }
            }
            else if (value is int)
            {
                node.InnerText = ((int) value).ToString();
            }
            else if (value is bool)
            {
                node.InnerText = ((bool) value) ? "1" : "0";
            }
            else if (string.IsNullOrEmpty(node.InnerXml))
            {
                var args = value as IList;
                if (args != null)
                {
                    foreach (var item in args)
                    {
                        OnNode(parentNode, name, item);
                    }
                }
                else if(value != null)
                {
                    node.InnerText = value.ToString();
                }
            }
            parentNode.AppendChild(node);
        }

        public virtual XmlDocument ToXml()
        {
            _xml = new XmlDocument();
            var ps = GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var root = CreateXmlNode(GetRootName());
            _xml.AppendChild(root);

            foreach (var item in _order)
            {
                var p = ps.FirstOrDefault(i => i.Name.OICEquals(item));
                if (p != null)
                {
                    var v = p.GetValue(this, null);
                    if (v == null && Requires.Contains(p.Name))
                        throw new ArgumentNullException(p.Name);
                    OnNode(root, p.Name, v);
                }
            }
            return _xml;
        }
    }
}
